#include "../lib/fcp.hpp"
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <cmath>
#include <set>
#include <queue>

using namespace std;

void FCP::doFCP(Graph &g)
{
    expand(g);
    map(g);
}


static void expandHelper(Node *n, Graph &g)
{
    if(n == NULL)
    {
        return;
    }
    if(n->property("expanded") != "True")
    {
        uint rProc = n->propertyAsInt("proc");
        uint rNet = n->propertyAsInt("net");
        size_t hostsRequired = ceil(1.0*rProc/NODE_SPEED);

        if( hostsRequired < ceil(1.0*rNet/NODE_NET))
        {
            hostsRequired  = ceil(1.0*rNet/NODE_NET);
        }

        n->addProperty("copies", boost::lexical_cast<std::string>(hostsRequired));
        n->addProperty("expanded", "True");
        for(size_t i = 0; i < hostsRequired - 1; i++)
        {
            Node* newNode = new Node(n->name_ + "_copy" + boost::lexical_cast<string>(i+1));
            for(size_t j = 0; j < n->childeren_.size(); j++)
            {
                newNode->joinChild(n->childeren_[j]);
            }
            for(size_t j = 0; j < n->parrents_.size(); j++)
            {
                newNode->joinParrent(n->parrents_[j]);
            }
            newNode->setProperties(n->properties());
            newNode->addProperty("expanded", "True");
            g.addNode(newNode);
        }
    }
    for(size_t i = 0; i< n->childeren_.size(); i++)
    {
        expandHelper(n->childeren_[i], g);
    }
}

void FCP::expand(Graph &g)
{
    Node* root = g.source();
    expandHelper(root, g);
}

size_t FCP::minHosts(Graph &g)
{
    Graph::node_it it;
    uint tProc = 0;
    uint tNet = 0;
    for(it = g.nodes_.begin(); it!=g.nodes_.end(); it++)
    {
        tProc += it->second->propertyAsInt("proc")/it->second->propertyAsInt("copies");
        tNet += it->second->propertyAsInt("net")/it->second->propertyAsInt("copies");
    }
    size_t hostsRequired = ceil(1.0*tProc/NODE_SPEED);

    if( hostsRequired < ceil(1.0*tNet/NODE_NET))
    {
        hostsRequired  = ceil(1.0*tNet/NODE_NET);
    }
    return hostsRequired;
}

struct HostStats
{
    int proc;
    int net;
    set<Node*> tasks;
};

struct HostScore
{
    int proc;
    int net;
};

static void mapHelper(Node* n, queue<Node*> &next, HostStats* h, size_t h_size)
{
    if(n->property("placed") != "True")
    {
        uint proc = n->propertyAsInt("proc")/n->propertyAsInt("copies");
        uint net = n->propertyAsInt("net")/n->propertyAsInt("copies");
        //uint netIn = n->propertyAsInt("netin")/n->propertyAsInt("copies");
        //uint netOut = n->propertyAsInt("netout")/n->propertyAsInt("copies");
        HostScore scores[h_size];
        for(size_t i = 0; i < h_size; i++)
        {
            scores[i].proc = proc;
            scores[i].net = net;
            for(size_t j = 0; j < n->parrents_.size(); j++)
            {
                set<Node*>::iterator it = h[i].tasks.find(n->parrents_[j]);
                if(it != h[i].tasks.end())
                {
                    Node* child = *it;
                    uint childNetOut = child->propertyAsInt("netout")/child->propertyAsInt("copies");
                    scores[i].net -= childNetOut;
                }
            }
        }
        int minHost = 0;
        for(size_t i = 0; i < h_size; i++)
        {
            if(h[i].proc >= scores[i].proc)
                if(h[i].net >= scores[i].net)
                    if(h[minHost].net > scores[i].net)
                        minHost = i;
        }
        h[minHost].tasks.insert(n);
        h[minHost].net -= scores[minHost].net;
        h[minHost].proc -= scores[minHost].proc;
        n->addProperty("host", boost::lexical_cast<string>(minHost));
        n->addProperty("placed", "True");
    }

    for(size_t i = 0; i< n->childeren_.size(); i++)
    {
        next.push(n->childeren_[i]);
    }
}

void FCP::map(Graph &g)
{
    size_t numHosts = FCP::minHosts(g);
    cout << "Workflow requires " << numHosts << " hosts" << endl;
    HostStats hosts[numHosts];
    g.properties_.insert(make_pair("hosts", boost::lexical_cast<string>(numHosts)));
    for(size_t i = 0; i < numHosts; i++)
    {
        hosts[i].proc = NODE_SPEED;
        hosts[i].net = NODE_NET;
    }
    queue<Node*> nodes;
    mapHelper(g.source(), nodes, hosts, numHosts);
    while(nodes.size() != 0)
    {
        Node* next = nodes.front();
        nodes.pop();
        mapHelper(next, nodes, hosts, numHosts);
    }
}
